{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第四节 链（chain）,输出解析器与记忆(memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#导入语言模型\n",
    "import os\n",
    "from langchain_community.llms import Tongyi\n",
    "from langchain_community.llms import SparkLLM\n",
    "from langchain_community.llms import QianfanLLMEndpoint\n",
    "\n",
    "import pandas as pd\n",
    "#导入模版\n",
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "#导入聊天模型\n",
    "from langchain.prompts.chat import (\n",
    "    ChatPromptTemplate,\n",
    "    SystemMessagePromptTemplate,\n",
    "    AIMessagePromptTemplate,\n",
    "    HumanMessagePromptTemplate,\n",
    ")\n",
    "from langchain.schema import (\n",
    "    AIMessage,\n",
    "    HumanMessage,\n",
    "    SystemMessage\n",
    ")\n",
    "\n",
    "from langchain_community.chat_models import ChatSparkLLM\n",
    "from langchain_community.chat_models.tongyi import ChatTongyi\n",
    "from langchain_community.chat_models import QianfanChatEndpoint\n",
    "\n",
    "#输入三个模型各自的key\n",
    "\n",
    "os.environ[\"DASHSCOPE_API_KEY\"] = \"\"\n",
    "\n",
    "os.environ[\"IFLYTEK_SPARK_APP_ID\"] = \"\"\n",
    "os.environ[\"IFLYTEK_SPARK_API_KEY\"] = \"\"\n",
    "os.environ[\"IFLYTEK_SPARK_API_SECRET\"] = \"\"\n",
    "\n",
    "os.environ[\"QIANFAN_AK\"] = \"\"\n",
    "os.environ[\"QIANFAN_SK\"] = \"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_ty = Tongyi(temperature=0.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 链(chain)\n",
    "\n",
    "只使用一个 LLM 去开发应用，比如聊天机器人是很简单的，但更多的时候，我们需要用到许多 LLM 去共同完成一个任务，这样原来的模式就不足以支撑这种复杂的应用。\n",
    "\n",
    "为此 LangChain 提出了 Chain 这个概念，也就是一个所有组件的序列，能够把一个个独立的 LLM 链接成一个组件，从而可以完成更复杂的任务。举个例子，我们可以创建一个 chain，用于接收用户的输入，然后使用提示词模板将其格式化，最后将格式化的结果输出到一个 LLM。通过这种链式的组合，就可以构成更多更复杂的 chain。\n",
    "\n",
    "在 LangChain 中有许多实现好的 chain，以最基础的 LLMChain 为例，它主要实现的就是接收一个提示词模板，然后对用户输入进行格式化，然后输入到一个 LLM，最终返回 LLM 的输出。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'当然可以，以下是我为您的彩色袜子公司提出的五个中文名称建议：\\n\\n1. 彩韵袜业：强调袜子的色彩丰富和韵律感，同时也暗示了产品的艺术性和时尚感。\\n2. 明彩足下：寓意穿上这些袜子能让人的脚步都明亮多彩，生活因此增添乐趣。\\n3. 色舞袜坊：形象地描绘出袜子色彩斑斓，仿佛在脚下跳舞的情景，富有活力和动感。\\n4. 七彩织梦：七彩代表各种颜色，织梦则象征着用色彩编织美好的梦想，富有诗意。\\n5. 彩悦足尖：强调袜子带给人们的快乐和满足感，同时“悦”字也传达了高品质的使用体验。\\n\\n希望这些建议能对您有所帮助！'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "template = \"\"\"我希望你能充当新公司的命名顾问。一个生产{product}的公司好的中文名字是什么,请给出五个选项？\"\"\"\n",
    "\n",
    "prompt = PromptTemplate(\n",
    "    input_variables=[\"product\"],\n",
    "    template=template,\n",
    ")\n",
    "model_ty.invoke(prompt.format(product=\"彩色袜子\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import LLMChain\n",
    " \n",
    "prompt = PromptTemplate(\n",
    "    input_variables=[\"product\"],\n",
    "    template=\"我希望你能充当新公司的命名顾问。一个生产{product}的公司好的中文名字是什么,请给出五个选项？\",\n",
    ")\n",
    "\n",
    "chain = LLMChain(llm=model_ty, prompt=prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'product': '彩色袜子',\n",
       " 'text': '1. 彩韵袜业：这个名字融合了“色彩”和“韵律”，寓意袜子的多彩和时尚，同时传达出一种活泼、灵动的品牌形象。\\n\\n2. 明彩足下：强调袜子的鲜艳色彩，同时“明”字也寓意产品质量明亮、清晰，给人以良好的视觉感受。\\n\\n3. 七彩足迹：以七彩代表丰富的颜色选择，\"足迹\"则暗示袜子与人们的日常生活紧密相连，每一步都充满色彩。\\n\\n4. 颜舞袜艺： \"颜舞\"寓意色彩的跳跃和生动，\"袜艺\"则强调制作工艺的艺术性和专业性。\\n\\n5. 色彩织梦：通过“色彩”和“织梦”结合，表达出产品能为人们的生活增添色彩，如同编织美好的梦想一般。'}"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke(\"彩色袜子\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 多个提示词"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'text': '当然可以，以下是我为您的袜子公司提出的五个中文名字建议：\\n\\n1. \"足悦\"：这个名字寓含了袜子带给人们的舒适和快乐，同时也表达了对顾客双脚的呵护。\\n2. \"行云袜业\"：行云象征流畅、自由，暗示袜子的柔软和无束缚感，同时也给人以轻松愉快的联想。\\n3. \"舒步\"：强调穿着的舒适感和行走的自在，适合注重品质的袜子品牌。\\n4. \"纤丝韵\"：纤丝代表袜子的细腻材质，韵则传达出一种雅致和时尚的氛围。\\n5. \"足尚工坊\"：将“足”与“时尚”结合，表示公司的产品既实用又潮流，工坊则带有一种手工艺的温馨感。\\n\\n希望这些建议能对您有所帮助！如果您需要更多选择或者有特定的要求，欢迎进一步告诉我。'},\n",
       " {'text': '当然可以，以下是我为您的电脑生产公司提出的五个可能的中文名字建议：\\n\\n1. \"智域科技\"：这个名字突出了智能和科技的元素，同时也传达出公司在科技领域的专业和广阔视野。\\n2. \"灵思电脑\"：\"灵思\"寓意创新思维和灵活应用，强调产品的智能化和人性化设计。\\n3. \"锐志数码\"：\"锐志\"表示公司具有坚定的进取心和敏锐的市场洞察力，\"数码\"则直接指代产品类别。\\n4. \"翔云计算机\"：\"翔云\"象征着自由、高效，寓意公司的产品能帮助用户如同在云端般轻松工作。\\n5. \"博睿创研\"：\"博睿\"意味着广博的知识和智慧，\"创研\"则强调了公司的研发创新精神。\\n\\n希望这些建议能对您有所帮助！在选择名字时，您还可以考虑公司的品牌定位、目标市场以及想要传达的品牌形象等因素。'},\n",
       " {'text': '1. 雄步天下：这个名字寓含了男士的步伐稳健，有领导力，如同踏遍天下，寓意鞋子的质量和舒适度能让消费者自信前行。\\n2. 风度翩翩：风度翩翩常用来形容男士的优雅气质，以此为名，可以强调产品的时尚感和品质感，适合注重风格的男士。\\n3. 铠甲足下：铠甲象征保护和力量，暗示鞋子能为双脚提供良好的保护，适合强调鞋的耐用性和舒适性。\\n4. 立绅之履：立绅寓意站立的绅士，代表了优雅、稳重，体现出男鞋的高端和品质，同时也暗示穿着者有良好的品格。\\n5. 品逸先生：品逸代表品味和超越，强调产品的独特设计和卓越品质，适合追求个性化和高品质生活的男士。'}]"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 也可以使用generate，apply的生成结果简洁点\n",
    "input_list = [\n",
    "    {\"product\": \"袜子\"},\n",
    "    {\"product\": \"电脑\"},\n",
    "    {\"product\": \"男鞋\"}\n",
    "]\n",
    "\n",
    "chain.apply(input_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 多个变量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = PromptTemplate(\n",
    "    input_variables=[\"product\"],\n",
    "    template=\"我希望你能充当新公司的命名顾问。公司新推出一款{product}产品，请帮给新产品取一个{style}中文名字，给出五个选项？\",\n",
    ")\n",
    "\n",
    "chain_m = LLMChain(llm=model_ty, prompt=prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'product': '混动越野汽车',\n",
       " 'style': '科技感',\n",
       " 'text': '当然可以，以下是我为您的混动越野汽车产品提出的五个具有科技感的中文名字选项：\\n\\n1. \"凌云智行\"：这个名字融合了凌云壮志的意象和智能行驶的概念，展现出汽车的高性能和科技特性。\\n\\n2. \"电掣星驰\"：电掣代表混合动力的速度感，星驰则寓意科技如星辰般引领前行，富有未来感。\\n\\n3. \"绿动未来\"：突出了环保的混动特点，同时“未来”一词暗示了科技的前瞻性。\\n\\n4. \"影豹锐动\"：影豹象征速度和力量，锐动强调了汽车的科技敏锐性和动感。\\n\\n5. \"智越星际\"：智越代表智能超越，星际则象征着探索未知的科技精神，适合一款高科技的越野车。\\n\\n希望这些建议能对您有所帮助！如果您需要更多的选择或者有其他特定的要求，欢迎告诉我。'}"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain_m.invoke({\"product\": \"混动越野汽车\",\"style\": \"科技感\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 聊天模型的链\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "chat_ty = ChatTongyi()\n",
    "\n",
    "human_message_prompt = HumanMessagePromptTemplate(\n",
    "        prompt=PromptTemplate(\n",
    "            template=\"我希望你能充当新公司的命名顾问。一个生产{product}的公司好的中文名字是什么,请给出五个选项？?\",\n",
    "            input_variables=[\"product\"],\n",
    "        )\n",
    "    )\n",
    "chat_prompt_template = ChatPromptTemplate.from_messages([human_message_prompt])\n",
    "chain_chat = LLMChain(llm=chat_ty, prompt=chat_prompt_template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'product': '彩色袜子',\n",
       " 'text': '当然可以，为您的彩色袜子公司起名是一项创意任务。以下是五个富有创意且适合的中文名字建议：\\n\\n1. 彩云袜业（Cǎi Yún Wà Yè）：\"彩云\"象征多彩和轻盈，与袜子的色彩丰富相呼应。\\n2. 花漾织语（Huā Yàng Zhī Yǔ）：\"花漾\"形容色彩斑斓，\"织语\"暗示手工编织，温馨而女性化。\\n3. 七彩丝履（Qī Cǎi Sī Lǚ）：\"七彩\"直接表达颜色多样，\"丝履\"显得优雅且与袜子相关。\\n4. 绚彩踏浪（Xuàn Cǎi Tà Làng）：\"绚彩\"代表丰富的色彩，\"踏浪\"有轻松行走的意象，适合运动或时尚定位。\\n5. 色彩足迹（Sè Cǎi Zú Jì）：简洁明了，强调色彩的同时，\"足迹\"让人联想到穿着袜子的生活场景。\\n\\n每个名字都有其独特含义和感觉，您可以根据公司的品牌形象、目标市场和个人喜好选择最适合的一个。'}"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain_chat.invoke({\"product\": \"彩色袜子\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "classwork 1\n",
    "\n",
    "1, 创建一个多变量模版，要求给你的小孩取中文名字，变量分别为：父亲名字，母亲名字，小孩性别，名字的要求\n",
    "\n",
    "2, 定义一个链，使用上面多变量模版，调用链，传入参数，输出结果\n",
    "\n",
    "3，传入多组参数，通过apply方法，批量调用链，输出结果\n",
    "\n",
    "4，定义一个包含聊天模型的链实现上述模版的结果输出\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 输出解析器（Output Parsers）\n",
    "\n",
    "语言模型输出的是文本，但很多时候，我们可能希望获取比纯文本更结构化的信息，输出解析器（Output Parsers）就可以帮助我们获取结构化信息。输出解析器是结构化语言模型响应的类，其必须实现两个主要方法：\n",
    "\n",
    "get_format_instructions() -> str：返回一个包含语言模型输出如何格式化的指令字符串。\n",
    "\n",
    "parse(str) -> Any：接受一个字符串（假设为语言模型的响应），并将其解析为某种结构。\n",
    "\n",
    "\n",
    "### 逗号分隔列表输出解析器CommaSeparatedListOutputParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers import CommaSeparatedListOutputParser\n",
    "\n",
    "output_parser = CommaSeparatedListOutputParser()\n",
    "format_instructions = output_parser.get_format_instructions()\n",
    "prompt = PromptTemplate(\n",
    "    template=\"请列出五个 {subject}.\\n{format_instructions}\",\n",
    "    input_variables=[\"subject\"],\n",
    "    partial_variables={\"format_instructions\": format_instructions}\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "PromptTemplate(input_variables=['subject'], partial_variables={'format_instructions': 'Your response should be a list of comma separated values, eg: `foo, bar, baz`'}, template='请列出五个 {subject}.\\n{format_instructions}')"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "input0 = prompt.format(subject=\"中国的名山\")\n",
    "output = model_ty.invoke(input0)\n",
    "res0=output_parser.parse(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['泰山', '黄山', '华山', '衡山', '嵩山']"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'请列出五个 中国的名山.\\nYour response should be a list of comma separated values, eg: `foo, bar, baz`'"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "input0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 日期时间解析器DatetimeOutputParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers import DatetimeOutputParser\n",
    "from langchain.chains import LLMChain\n",
    "\n",
    "output_parser = DatetimeOutputParser()\n",
    "template = \"\"\"Answer the users question:\n",
    "\n",
    "{question}\n",
    "\n",
    "{format_instructions}\"\"\"\n",
    "prompt = PromptTemplate.from_template(template, partial_variables={\"format_instructions\": output_parser.get_format_instructions()})\n",
    "chain = LLMChain(prompt=prompt, llm=model_ty)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'question': '中国建国是哪一年?', 'text': '1949-10-01T00:00:00.000000Z'}"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output = chain.invoke(\"中国建国是哪一年?\")\n",
    "output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "PromptTemplate(input_variables=['question'], partial_variables={'format_instructions': \"Write a datetime string that matches the following pattern: '%Y-%m-%dT%H:%M:%S.%fZ'.\\n\\nExamples: 1749-02-25T14:55:06.481247Z, 1301-10-09T07:35:09.530266Z, 0159-12-20T14:28:16.366900Z\\n\\nReturn ONLY this string, no other words!\"}, template='Answer the users question:\\n\\n{question}\\n\\n{format_instructions}')"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 结构化输出解析器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers import StructuredOutputParser, ResponseSchema"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "```json\n",
      "{\n",
      "\t\"name\": \"Alice\",\n",
      "\t\"age\": \"18\"\n",
      "}\n",
      "```\n",
      "{'name': 'Alice', 'age': '18'}\n"
     ]
    }
   ],
   "source": [
    "response_schemas = [\n",
    "    ResponseSchema(name=\"name\", description=\"学生的姓名\"),\n",
    "    ResponseSchema(name=\"age\", description=\"学生的年龄\")\n",
    "]\n",
    "output_parser = StructuredOutputParser.from_response_schemas(response_schemas)\n",
    "\n",
    "format_instructions = output_parser.get_format_instructions()\n",
    "prompt = PromptTemplate(\n",
    "    template=\"回答下面问题,注意不要显示键的描述信息.\\n{format_instructions}\\n{question}\",\n",
    "    input_variables=[\"question\"],\n",
    "    partial_variables={\"format_instructions\": format_instructions}\n",
    ")\n",
    "\n",
    "_input = prompt.format_prompt(question=\"给我一个女孩的名字?\")\n",
    "output = model_ty(_input.to_string())\n",
    "print(output)\n",
    "print(output_parser.parse(output))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'回答下面问题,注意不要显示键的描述信息.\\nThe output should be a markdown code snippet formatted in the following schema, including the leading and trailing \"```json\" and \"```\":\\n\\n```json\\n{\\n\\t\"name\": string  // 学生的姓名\\n\\t\"age\": string  // 学生的年龄\\n}\\n```\\n给我一个女孩的名字?'"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "_input.to_string()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### classwork 2\n",
    "\n",
    "1. classwork1中的取名应用，最后要求以列表的形式输出名字\n",
    "\n",
    "2. 修改取名应用，要求使用结构化输出解析器，结构化输出名字和对应的意义"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Memory\n",
    "\n",
    "大多数的 LLM 应用程序都会有一个会话接口，允许我们和 LLM 进行多轮的对话，并有一定的上下文记忆能力。但实际上，模型本身是不会记忆任何上下文的，只能依靠用户本身的输入去产生输出。而实现这个记忆功能，就需要额外的模块去保存我们和模型对话的上下文信息，然后在下一次请求时，把所有的历史信息都输入给模型，让模型输出最终结果。\n",
    "\n",
    "而在 LangChain 中，提供这个功能的模块就称为 Memory，用于存储用户和模型交互的历史信息。在 LangChain 中根据功能和返回值的不同，会有多种不同的 Memory 类型，主要可以分为以下几个类别：\n",
    "\n",
    "* 对话缓冲区内存（ConversationBufferMemory），最基础的内存模块，用于存储历史的信息\n",
    "* 对话缓冲器窗口内存（ConversationBufferWindowMemory），只保存最后的 K 轮对话的信息，因此这种内存空间使用会相对较少\n",
    "* 对话摘要内存（ConversationSummaryMemory），这种模式会对历史的所有信息进行抽取，生成摘要信息，然后将摘要信息作为历史信息进行保存。\n",
    "* 对话摘要缓存内存（ConversationSummaryBufferMemory），这个和上面的作用基本一致，但是有最大 token 数的限制，达到这个最大 token 数的时候就会进行合并历史信息生成摘要\n",
    "\n",
    "值得注意的是，对话摘要内存的设计出发点就是语言模型能支持的上下文长度是有限的（一般是 2048），超过了这个长度的数据天然的就被截断了。这个类会根据对话的轮次进行合并，默认值是 2，也就是每 2 轮就开启一次调用 LLM 去合并历史信息。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 聊天消息历史 (ChatMessageHistory)\n",
    "\n",
    "大多数（如果不是全部）内存模块的核心实用类之一是 ChatMessageHistory 类。这是一个超轻量级的包装器，它公开了方便的方法来保存人类消息、AI 消息，然后获取它们全部。\n",
    "如果您在链外管理内存，可能需要直接使用此类。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.memory import ChatMessageHistory\n",
    "\n",
    "history = ChatMessageHistory()\n",
    "history.add_user_message(\"hi!\")\n",
    "history.add_ai_message(\"whats up?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[HumanMessage(content='hi!'), AIMessage(content='whats up?')]"
      ]
     },
     "execution_count": 92,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "history.messages"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 对话缓冲区内存（ConversationBufferMemory）\n",
    "\n",
    "让我们看看如何在链中使用这个模块，其中我们设置了verbose=True以便查看提示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import ConversationChain\n",
    "from langchain.memory import ConversationBufferMemory\n",
    "\n",
    "conversation = ConversationChain(\n",
    "    llm=model_ty, \n",
    "    verbose=True, \n",
    "    memory=ConversationBufferMemory()\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n",
      "\n",
      "Current conversation:\n",
      "\n",
      "Human: Hi there!\n",
      "AI:\u001b[0m\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "\"Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\""
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation.predict(input=\"Hi there!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n",
      "\n",
      "Current conversation:\n",
      "Human: Hi there!\n",
      "AI: Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\n",
      "Human: 你是谁\n",
      "AI:\u001b[0m\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？'"
      ]
     },
     "execution_count": 98,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation.predict(input=\"你是谁\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n",
      "\n",
      "Current conversation:\n",
      "Human: Hi there!\n",
      "AI: Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\n",
      "Human: 你是谁\n",
      "AI: 我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？\n",
      "Human: 你的背后原理是什么\n",
      "AI:\u001b[0m\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\\n\\n我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。'"
      ]
     },
     "execution_count": 99,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation.predict(input=\"你的背后原理是什么\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n",
      "\n",
      "Current conversation:\n",
      "Human: Hi there!\n",
      "AI: Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\n",
      "Human: 你是谁\n",
      "AI: 我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？\n",
      "Human: 你的背后原理是什么\n",
      "AI: 我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\n",
      "\n",
      "我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。\n",
      "Human: 能简单点回答吗\n",
      "AI:\u001b[0m\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'input': '能简单点回答吗',\n",
       " 'history': \"Human: Hi there!\\nAI: Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\\nHuman: 你是谁\\nAI: 我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？\\nHuman: 你的背后原理是什么\\nAI: 我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\\n\\n我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。\",\n",
       " 'response': '当然可以！我是通过深度学习来理解和生成人类语言的AI。我被训练成能看懂各种信息，然后用这些信息给出回答。当你问我问题时，我会分析问题意思，查找相关知识，然后给你一个答案。但记得，我对最新事情可能不太了解哦。'}"
      ]
     },
     "execution_count": 100,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation.invoke(\"能简单点回答吗\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[HumanMessage(content='Hi there!'),\n",
       " AIMessage(content=\"Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\"),\n",
       " HumanMessage(content='你是谁'),\n",
       " AIMessage(content='我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？'),\n",
       " HumanMessage(content='你的背后原理是什么'),\n",
       " AIMessage(content='我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\\n\\n我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。'),\n",
       " HumanMessage(content='能简单点回答吗'),\n",
       " AIMessage(content='当然可以！我是通过深度学习来理解和生成人类语言的AI。我被训练成能看懂各种信息，然后用这些信息给出回答。当你问我问题时，我会分析问题意思，查找相关知识，然后给你一个答案。但记得，我对最新事情可能不太了解哦。')]"
      ]
     },
     "execution_count": 101,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation.memory.chat_memory.messages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 保存消息历史\n",
    "\n",
    "您经常需要保存消息，然后加载它们以便再次使用。这可以通过先将消息转换为普通的 Python 字典，保存这些字典（如 json 或其他格式），然后加载它们来轻松完成。以下是一个示例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "from langchain.memory import ChatMessageHistory\n",
    "from langchain.schema import messages_from_dict, messages_to_dict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatMessageHistory(messages=[HumanMessage(content='Hi there!'), AIMessage(content=\"Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\"), HumanMessage(content='你是谁'), AIMessage(content='我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？'), HumanMessage(content='你的背后原理是什么'), AIMessage(content='我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\\n\\n我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。'), HumanMessage(content='能简单点回答吗'), AIMessage(content='当然可以！我是通过深度学习来理解和生成人类语言的AI。我被训练成能看懂各种信息，然后用这些信息给出回答。当你问我问题时，我会分析问题意思，查找相关知识，然后给你一个答案。但记得，我对最新事情可能不太了解哦。'), HumanMessage(content='Hi there!'), AIMessage(content=\"Hello! How can I help you today? Whether you have questions about science, entertainment, history, or anything else, feel free to ask. I'm here to provide information and engage in an interesting conversation!\")])"
      ]
     },
     "execution_count": 125,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation.memory.chat_memory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "dicts = messages_to_dict(conversation.memory.chat_memory.messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'type': 'human',\n",
       "  'data': {'content': 'Hi there!',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'human',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'ai',\n",
       "  'data': {'content': \"Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\",\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'ai',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'human',\n",
       "  'data': {'content': '你是谁',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'human',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'ai',\n",
       "  'data': {'content': '我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'ai',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'human',\n",
       "  'data': {'content': '你的背后原理是什么',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'human',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'ai',\n",
       "  'data': {'content': '我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\\n\\n我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'ai',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'human',\n",
       "  'data': {'content': '能简单点回答吗',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'human',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}},\n",
       " {'type': 'ai',\n",
       "  'data': {'content': '当然可以！我是通过深度学习来理解和生成人类语言的AI。我被训练成能看懂各种信息，然后用这些信息给出回答。当你问我问题时，我会分析问题意思，查找相关知识，然后给你一个答案。但记得，我对最新事情可能不太了解哦。',\n",
       "   'additional_kwargs': {},\n",
       "   'type': 'ai',\n",
       "   'name': None,\n",
       "   'id': None,\n",
       "   'example': False}}]"
      ]
     },
     "execution_count": 106,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dicts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "\n",
    "f = open(\"./memory\", 'wb')\n",
    "pickle.dump(dicts,f)\n",
    "f.close()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 142,
   "metadata": {},
   "outputs": [],
   "source": [
    "dicts_load = pickle.load(open(\"./memory\", \"rb\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "metadata": {},
   "outputs": [],
   "source": [
    "#dicts_load"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 向对话添加记忆\n",
    "\n",
    "https://stackoverflow.com/questions/75965605/how-to-persist-langchain-conversation-memory-save-and-load"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 145,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_messages = messages_from_dict(dicts_load)\n",
    "retrieved_chat_history = ChatMessageHistory(messages=new_messages)\n",
    "retrieved_memory = ConversationBufferMemory(chat_memory=retrieved_chat_history)\n",
    "\n",
    "conversation_reload = ConversationChain(\n",
    "    llm=model_ty, \n",
    "    verbose=True, \n",
    "    memory=retrieved_memory\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 146,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new ConversationChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3mThe following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.\n",
      "\n",
      "Current conversation:\n",
      "Human: Hi there!\n",
      "AI: Hello! How can I assist you today? I'm here to provide information or engage in a chat about virtually any topic. Feel free to ask me anything, from science and technology to entertainment, history, or even personal advice. Just let me know what's on your mind!\n",
      "Human: 你是谁\n",
      "AI: 我是来自阿里云的大规模语言模型，我叫通义千问。我是一个能够理解和生成人类语言的AI系统，旨在帮助用户解答问题、提供信息或进行多方面的对话。有什么我可以帮助你的吗？\n",
      "Human: 你的背后原理是什么\n",
      "AI: 我的背后原理主要基于深度学习和自然语言处理技术。具体来说，我是一个预训练的Transformer模型，训练数据来自于大量的文本语料库，这些语料库包含了各种各样的主题和文体。在训练过程中，我学会了捕捉语言的模式和结构，理解上下文，并生成连贯、相关的回应。\n",
      "\n",
      "我的工作流程大致如下：当接收到一个问题或输入时，我会分析句子的结构，理解其含义，并在内部的词汇表和知识库中搜索相关信息。然后，我使用这些信息来生成一个回答，这个回答尽可能地贴切、准确和有意义。不过，需要注意的是，虽然我努力提供最准确的答案，但我的知识可能具有一定的时效限制，对于最新发生的事情或最新科研成果，我可能并不了解。\n",
      "Human: 能简单点回答吗\n",
      "AI: 当然可以！我是通过深度学习来理解和生成人类语言的AI。我被训练成能看懂各种信息，然后用这些信息给出回答。当你问我问题时，我会分析问题意思，查找相关知识，然后给你一个答案。但记得，我对最新事情可能不太了解哦。\n",
      "Human: 我回来了\n",
      "AI:\u001b[0m\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'欢迎回来！有什么新问题或者需要继续之前的对话吗？我在这里随时准备帮助你。'"
      ]
     },
     "execution_count": 146,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conversation_reload.predict(input=\"我回来了\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### classwork 3\n",
    "\n",
    "1. 请通过ConversationChain创建一个带记忆的对话，并使用该对话进行多次对话\n",
    "\n",
    "2. 请将上述对话的上下文保存下来\n",
    "\n",
    "3. 读取保存文件，请将上述对话的上下文加载到一个新的对话中"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
